/** \file    bmp.h
  * \brief   Input/output of images in uncompressed BMP file format.
  * \date    2001 - 2003
  * \author  Pavel Agapov, Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/include/imglib/bmp.h,v 1.3 2004/06/05 14:16:27 cscom1r4 Exp $
  */

#if !defined(IMGLIB_BMP_H)
#define IMGLIB_BMP_H

#include <fstream>
#include <biostream>
#include <functional>

#include "imglib/image.h"
#include "imglib/color.h"
#include "imglib/bitmap.h"
#include "imglib/dichromatic_raster.h"
#include "imglib/io_error.h"
#include "imglib/io_types.h"

namespace img {

class bitmap;

namespace bmp {

///////////
// types //
///////////

const xtd::int16u	BM_TYPE = 0x4d42;  // 0x42 = "B" 0x4d = "M"
const xtd::int32u	BM_RGB	= 0;

#pragma pack (push, 2)

struct file_header {
	xtd::int16u		type;
	xtd::int32u		size;
	xtd::int16u		reserved1;
	xtd::int16u		reserved2;
	xtd::int32u		offbits;
};

struct info_header {
	xtd::int32u		size;
	xtd::int32s		width;
	xtd::int32s		height;
	xtd::int16u		planes;
	xtd::int16u		bit_count;
	xtd::int32u		compression;
	xtd::int32u		size_image;
	xtd::int32s		x_pels_per_meter;
	xtd::int32s		y_pels_per_meter;
	xtd::int32u		clr_used;
	xtd::int32u		clr_important;
};

typedef xtd::int8u byte;

typedef BGRA<byte> color;
typedef color rgb_quad;
typedef BGR <byte> colorx;

/////////////
// palette //
/////////////

struct bitmap_palette : public std::unary_function<bool, color> {
	rgb_quad background;
	rgb_quad foreground;
	color operator () (bool bit) const
		{ return bit ? foreground : background; }
};

#pragma pack (pop)	 

const struct grey_palette_t {} grey_palette = {};

class palette : public std::unary_function<byte, color> {
private:
	std::vector<rgb_quad> _entries;
public:
	palette(unsigned int count = 0)
		: _entries(count) { }
	palette(const grey_palette_t &)
		: _entries(256)
	  { for (int i = 0; i != 256; ++i) 
		  _entries[i] = rgb_quad(i, i, i, 0); }
	unsigned int size() const
		{ return _entries.size(); }
	friend xtd::bistream & operator >> (xtd::bistream & bin, palette & pal) {
		bin.read(reinterpret_cast<char *>(&*pal._entries.begin()), sizeof(rgb_quad) * pal.size()); 
		return bin;
	}
	friend xtd::bostream & operator << (xtd::bostream & bout, const palette & pal) {
		bout.write(reinterpret_cast<const char *>(&*pal._entries.begin()), sizeof(rgb_quad) * pal.size()); 
		return bout;
	}
	color operator () (byte index) const {
		if (index >= _entries.size())
			throw bad(bad::palette_index, index);
		return _entries[index];
	}
};

__declspec(noreturn) inline void read_throw(const char * filename = "") {
	throw io_error("could not open BMP image for reading or read error", filename);
}

//////////
// read //
//////////

///generic read from file
template <class I> 
void read(const char * filename, I & img) 
{
	try {
		std::filebuf file;

		#if defined __GNUC__
		//buffer may increase IO speed in some STL implementations
		char buffer[16*1024];
		file.pubsetbuf(buffer, 16*1024);
		#endif 

		file.open(filename, std::ios::in | std::ios::binary);

		xtd::bistream bin(&file);
		bin.exceptions(std::ios::badbit|std::ios::failbit);

		read(bin, img);
	}
	catch (io_error & e) {
		if (e.filename().empty())
			e.filename(filename);
		throw;
	}
	catch (const std::exception &) {
		read_throw(filename);
	}
}

template <class I> 
inline void read(const std::string & path, I & img) 
	{ read(path.c_str(), img); }

class reader {
public:
	xtd::bistream & bin;
	file_header bf;
	info_header	bi;
	std::ios::pos_type pos;
	
	reader(xtd::bistream & _bin) : bin(_bin)
	{
		pos = bin.tellg();
		bin.get(bf);
		bin.get(bi);

		if (bf.type != BM_TYPE)
			throw unsupported(unsupported::file_type, bf.type);
		if (bi.compression != BM_RGB)
			throw unsupported(unsupported::compression, bi.compression);
		if (bi.width < 0)
			throw unsupported(unsupported::width, bi.width);
	}

	template <class I>
	void contents(I & img) {
		if (bi.height < 0) {
			img.order(top_down);
			bi.height = - bi.height;
		} else
			img.order(bottom_up);
		img.resize(bi.width, bi.height, 4);
		//this check gives significant performance boost on STLport with its slow seekg(),read() combination
		if (bin.tellg() != pos + std::ios::off_type(bf.offbits))
			bin.seekg(pos + std::ios::off_type(bf.offbits));
		bin.read(reinterpret_cast<char *>(img.data()), img.bytes());
	}
};

const struct in1bit_t {}  in1bit = {};
const struct in8bit_t {}  in8bit = {};
const struct in24bit_t {} in24bit= {};
const struct in32bit_t {} in32bit= {};

template <class B, class I> 
void read_contents(B, I & img, reader & r);

template <class I> 
void read_contents(in1bit_t, I & img, reader & r) 
{
	bitmap_palette pal;
	r.bin.get(pal);
	bitmap tmp;
	r.contents(tmp);
	img.clear();
	img.order(tmp.order());
	img = modify(tmp, pal);
}

template <> 
inline void read_contents(in1bit_t, bitmap & img, reader & r) 
{
	bitmap_palette pal;
	r.bin.get(pal);
	r.contents(img);
	if (!pal.background.is_black() && pal.foreground.is_black())
		img.inverse();
}

template <> 
inline void read_contents(in1bit_t, image<byte> & img, reader & r) 
{
	bitmap_palette pal;
	r.bin.get(pal);
	bitmap tmp;
	r.contents(tmp);
	img.clear();
	img.order(tmp.order());

	byte val0 = 0, val1 = 255;
	if (!pal.background.is_black() && pal.foreground.is_black())
		std::swap(val0, val1);
	img = make_dichromatic_raster(tmp, val0, val1);
}

template <class I> 
void read_contents(in8bit_t, I & img, reader & r) 
{
	if (r.bi.clr_used == 0)
		r.bi.clr_used = 256;
	palette pal(r.bi.clr_used);
	r.bin >> pal;
	image<byte> tmp;
	r.contents(tmp);
	img.copy_attributes(tmp);
	img = modify(tmp, pal);
}

template <> 
inline void read_contents(in8bit_t, image<byte> & img, reader & r) 
{
	if (r.bi.clr_used == 0)
		r.bi.clr_used = 256;
	palette pal(r.bi.clr_used);
	r.bin >> pal;
	r.contents(img);
}

template <> 
inline void read_contents(in8bit_t, bitmap & img, reader & r) 
{
	throw unsupported(unsupported::bit_count, r.bi.bit_count);
}

template <class I> 
inline void read_contents(in24bit_t, I & img, reader & r) 
{
	image<colorx> tmp;
	r.contents(tmp);
	img.copy_attributes(tmp);
	img = tmp;
}

template <> 
inline void read_contents(in24bit_t, image<colorx> & img, reader & r) 
{
	r.contents(img);
}

template <> 
inline void read_contents(in24bit_t, bitmap & img, reader & r) 
{
	throw unsupported(unsupported::bit_count, r.bi.bit_count);
}

template <> 
inline void read_contents(in24bit_t, image<byte> & img, reader & r) 
{
	throw unsupported(unsupported::bit_count, r.bi.bit_count);
}

template <class I> 
inline void read_contents(in32bit_t, I & img, reader & r) 
{
	image<color> tmp;
	r.contents(tmp);
	img.copy_attributes(tmp);
	img = tmp;
}

template <> 
inline void read_contents(in32bit_t, image<color> & img, reader & r) 
{
	r.contents(img);
}

template <> 
inline void read_contents(in32bit_t, bitmap & img, reader & r) 
{
	throw unsupported(unsupported::bit_count, r.bi.bit_count);
}

template <> 
inline void read_contents(in32bit_t, image<byte> & img, reader & r) 
{
	throw unsupported(unsupported::bit_count, r.bi.bit_count);
}

///genereic read from binary stream
template <class I> 
void read(xtd::bistream & bin, I & img) 
{
	reader r(bin);

	switch (r.bi.bit_count) {
	case 1:	
		read_contents(in1bit, img, r);
		return;
	case 4:
	default:
		throw unsupported(unsupported::bit_count, r.bi.bit_count);
	case 8:
		read_contents(in8bit, img, r);
		return;
	case 24: 
		read_contents(in24bit, img, r);
		return;
	case 32:
		read_contents(in32bit, img, r);
		return;
	}
}

template <> 
inline void read(xtd::bistream & bin, info_header & info) {
	file_header fh;
	bin.get(fh);
	bin.get(info);
}

//////////
// ping //
//////////

inline bool ping(const std::string & path, file_info * info = 0)
{
	try {
		std::filebuf file;
		file.open(path.c_str(), std::ios::in | std::ios::binary);
		xtd::bistream bin(&file);
		bin.exceptions(std::ios::badbit|std::ios::failbit);
		reader r(bin);

		switch (r.bi.bit_count) {
		case 1:
		case 8:
		case 24:
		case 32:
			break;
		default:
			return false;
		}

		if (info) {
			info->path = path;
			info->format = "BMP";
			info->dim = size_extent(r.bi.width, r.bi.height);
			info->bit_count = r.bi.bit_count;
			info->compressed = r.bi.compression == BM_RGB;
		}

		return true;
	}
	catch (const std::exception &) {
		return false;
	}
}

///////////
// write //
///////////

class writer {
public:
	xtd::bostream & bout;
	file_header bf;
	info_header	bi;
	
	writer(xtd::bostream & _bout) : bout(_bout)
	{
		memset(&bf, 0, sizeof(file_header));
		bf.type = BM_TYPE;
		bf.offbits = sizeof(file_header) + sizeof(info_header);

		memset(&bi, 0, sizeof(info_header));
		bi.size = sizeof(info_header);
		bi.planes = 1;
		bi.compression = BM_RGB;
	}

	template <class I>
	void header(I & img) {
		bf.size = bf.offbits + img.bytes();
		bi.width = img.width();
		bi.height = //(img.order() == bottom_up ? img.height() : - img.height());
			img.height();//otherwise it can't be viewed
		bout.put(bf);
		bout.put(bi);
	}

	template <class I>
	void body(I & img) {
		bout.write(reinterpret_cast<const char *>(img.data()), img.bytes());
	}

	template <class I>
	void everything(I & img) {
		header(img);
		body(img);
	}

	template <>
	void everything(image<byte> & img) {
		//add palette size
		bf.size += 256 * 4; 
		bf.offbits += 256 * 4; 

		header(img);
		bout << palette(grey_palette);
		body(img);
	}
};

template <class I> 
struct write_to_stream {
	//template typedef is allowed only inside a class
	typedef void (*func)(xtd::bostream &, const I &);
};

///generic write to file
template <class I> 
size_t write(const char * filename, const I & img, typename write_to_stream<I>::func f)
{
	std::filebuf file;

	#if defined __GNUC__
	//buffer may increase IO speed in some STL implementations
	char buffer[16*1024];
	file.pubsetbuf(buffer, 16*1024);
	#endif 

	file.open(filename, std::ios::out | std::ios::binary);

	xtd::bostream bout(&file);
	bout.exceptions(std::ios::badbit|std::ios::failbit);

	f(bout, img);

	return bout.tellp();
}

//////////////////
// write 32 bit //
//////////////////

template <class I> 
inline void write32bit(xtd::bostream & bout, const I & img) 
{
	image<color> tmp(img.width(), img.height(), 4);
	tmp.order(bottom_up);
	tmp = img;
	write32bit(bout, tmp);
}

template <> 
inline void write32bit(xtd::bostream & bout, const image<color> & img) 
{
	writer w(bout);
	w.bi.bit_count = 32;

	unsigned a = img.alignment();
	if ((a == 4 || a <= 2) && img.order() == bottom_up)
		w.everything(img);
	else {
		image<color> tmp(img.width(), img.height(), 4);
		tmp.order(bottom_up);
		tmp = img;
		w.everything(tmp);
	}
}

template <class I> 
inline size_t write32bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write32bit;
	return write(filename, img, f);
}

//////////////////
// write 24 bit //
//////////////////

template <class I> 
inline void write24bit(xtd::bostream & bout, const I & img) 
{
	image<colorx> tmp(img.width(), img.height(), 4);
	tmp.order(bottom_up);
	tmp = img;
	write24bit(bout, tmp);
}

template <> 
inline void write24bit(xtd::bostream & bout, const image<colorx> & img) 
{
	writer w(bout);
	w.bi.bit_count = 24;

	if (img.alignment() == 4 && img.order() == bottom_up)
		w.everything(img);
	else {
		image<colorx> tmp(img.width(), img.height(), 4);
		tmp.order(bottom_up);
		tmp = img;
		w.everything(tmp);
	}
}

template <class I> 
inline size_t write24bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write24bit;
	return write(filename, img, f);
}

/////////////////
// write 8 bit //
/////////////////

template <class I> 
inline void write8bit(xtd::bostream & bout, const I & img) 
{
	image<byte> tmp(img.width(), img.height(), 4);
	tmp.order(bottom_up);
	tmp = img;
	write8bit(bout, tmp);
}

template <> 
inline void write8bit(xtd::bostream & bout, const image<byte> & img) 
{
	writer w(bout);
	w.bi.bit_count = 8;
	
	if (img.alignment() == 4 && img.order() == bottom_up)
		w.everything(img);
	else {
		image<byte> tmp(img.width(), img.height(), 4);
		tmp.order(bottom_up);
		tmp = img;
		w.everything(tmp);
	}
}

template <class I> 
inline size_t write8bit(const char * filename, const I & img) {	
	typename write_to_stream<I>::func f = write8bit;
	return write(filename, img, f);
}

/////////////////
// write 1 bit //
/////////////////

template <class I> 
inline void write1bit(xtd::bostream & bout, const I & img) 
{
	bitmap tmp(img.width(), img.height(), 4);
	tmp.order(bottom_up);
	tmp = img;
	write1bit(bout, tmp);
}

template <> 
inline void write1bit(xtd::bostream & bout, const bitmap & img) 
{
	writer w(bout);
	w.bf.offbits += sizeof(bitmap_palette);
	w.bi.bit_count = 1;

	if (img.alignment() == 4 && img.order() == bottom_up) {
		w.header(img);
		bitmap_palette pal;
		pal.background = color(0, 0, 0);
		pal.foreground = color(255, 255, 255);
		bout.put(pal);
		w.body(img);
	}
	else {
		bitmap tmp(img.width(), img.height(), 4);
		tmp.order(bottom_up);
		tmp = img;

		w.header(tmp);
		bitmap_palette pal;
		pal.background = color(0, 0, 0);
		pal.foreground = color(255, 255, 255);
		bout.put(pal);
		w.body(tmp);
	}
}

template <class I> 
inline size_t write1bit(const char * filename, const I & img) {
	typename write_to_stream<I>::func f = write1bit;
	return write(filename, img, f);
}

} //namespace bmp
} //namespace img

#endif // IMGLIB_BMP_H
